﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Katamino
{
    public class PentaFactory
    {
        private int width;
        private int height;
        public int Width { get { return width; } }
        public int Height { get { return height; } }

        private UInt64 col1; // vector with 1's in the first column of the Board.

        public List<Penta> Variations
        {
            get
            {
                if ((kataminos == null) || (kataminos.Count == 0))
                {
                    kataminos = CreateAll();
                }
                return kataminos;
            }
        }

        private List<Penta> kataminos;
        private PentaFactory pentafactory;
        public PentaFactory GetInstance
        {
            get
            {
                pentafactory = pentafactory ?? new PentaFactory(5, 12);
                return pentafactory;
            }
        }

        /// <summary>
        /// Define a bit vector of n ones.
        /// </summary>
        /// <param name="n">Between 0 and 63</param>
        /// <returns>UInt64 with the bitvector</returns>
        private UInt64 ones(int n)
        {
            return (((UInt64)1) << n) - 1;
        }

        public PentaFactory(int w, int h)
        {
            width = w;
            height = h;
            col1 = ones(w * h) / ones(w);
            kataminos = new List<Penta>();
        }

        public PentaFactory(Board b)
        {
            width = b.Width;
            height = b.Height;
            col1 = ones(b.Width * b.Height) / ones(b.Width);
            kataminos = new List<Penta>();
        }

        public Penta CreateKatamino(int id, int variation, int w, int h, UInt64 r1, UInt64 r2 = 0, UInt64 r3 = 0, UInt64 r4 = 0, UInt64 r5 = 0)
        {
            // only create the penta if it will fit the Board.
            if (w <= Width && h <= Height)
            {
                // Allowed positions on first row only.
                var row1 = ones(Width) >> (w - 1);
                UInt64 allowed = (col1 * row1) >> ((h - 1) * (Width));
                UInt64 bits = r1 + (r2 << Width) + (r3 << 2 * Width) + (r4 << 3 * Width) + (r5 << 4 * Width);

                int lshift = 0;
                while ((r1 > 0) && (r1 & 1) != 1)
                {
                    lshift++;
                    r1 = r1 >> 1;
                }

                var p = new Penta(id, variation, bits, allowed, lshift);

                kataminos.Add(p);

                return p;
            }
            else
            {
                return null;
            }
        }

        public List<Penta> CreateAll()
        {
            kataminos.Clear();

            CreateKatamino(1, 1, 5, 1, 31, 0, 0, 0, 0);
            CreateKatamino(1, 2, 1, 5, 1, 1, 1, 1, 1);

            CreateKatamino(2, 1, 4, 2, 15, 1, 0, 0, 0);
            CreateKatamino(2, 2, 4, 2, 15, 8, 0, 0, 0);
            CreateKatamino(2, 3, 4, 2, 1, 15, 0, 0, 0);
            CreateKatamino(2, 4, 4, 2, 8, 15, 0, 0, 0);
            CreateKatamino(2, 5, 2, 4, 3, 1, 1, 1, 0);
            CreateKatamino(2, 6, 2, 4, 3, 2, 2, 2, 0);
            CreateKatamino(2, 7, 2, 4, 1, 1, 1, 3, 0);
            CreateKatamino(2, 8, 2, 4, 2, 2, 2, 3, 0);

            CreateKatamino(3, 1, 4, 2, 15, 2, 0, 0, 0);
            CreateKatamino(3, 2, 4, 2, 15, 4, 0, 0, 0);
            CreateKatamino(3, 3, 4, 2, 2, 15, 0, 0, 0);
            CreateKatamino(3, 4, 4, 2, 4, 15, 0, 0, 0);
            CreateKatamino(3, 5, 2, 4, 1, 3, 1, 1, 0);
            CreateKatamino(3, 6, 2, 4, 2, 3, 2, 2, 0);
            CreateKatamino(3, 7, 2, 4, 1, 1, 3, 1, 0);
            CreateKatamino(3, 8, 2, 4, 2, 2, 3, 2, 0);

            CreateKatamino(4, 1, 4, 2, 7, 12, 0, 0, 0);
            CreateKatamino(4, 2, 4, 2, 12, 7, 0, 0, 0);
            CreateKatamino(4, 3, 4, 2, 3, 14, 0, 0, 0);
            CreateKatamino(4, 4, 4, 2, 14, 3, 0, 0, 0);
            CreateKatamino(4, 5, 2, 4, 1, 1, 3, 2, 0);
            CreateKatamino(4, 6, 2, 4, 1, 3, 2, 2, 0);
            CreateKatamino(4, 7, 2, 4, 2, 2, 3, 1, 0);
            CreateKatamino(4, 8, 2, 4, 2, 3, 1, 1, 0);

            CreateKatamino(5, 1, 3, 2, 3, 7, 0, 0, 0);
            CreateKatamino(5, 2, 3, 2, 6, 7, 0, 0, 0);
            CreateKatamino(5, 3, 3, 2, 7, 3, 0, 0, 0);
            CreateKatamino(5, 4, 3, 2, 7, 6, 0, 0, 0);
            CreateKatamino(5, 5, 2, 3, 1, 3, 3, 0, 0);
            CreateKatamino(5, 6, 2, 3, 2, 3, 3, 0, 0);
            CreateKatamino(5, 7, 2, 3, 3, 3, 1, 0, 0);
            CreateKatamino(5, 8, 2, 3, 3, 3, 2, 0, 0);

            CreateKatamino(6, 1, 3, 3, 2, 7, 2, 0, 0);

            CreateKatamino(7, 1, 3, 3, 1, 3, 6, 0, 0);
            CreateKatamino(7, 2, 3, 3, 3, 6, 4, 0, 0);
            CreateKatamino(7, 3, 3, 3, 4, 6, 3, 0, 0);
            CreateKatamino(7, 4, 3, 3, 6, 3, 1, 0, 0);

            CreateKatamino(8, 1, 3, 3, 1, 7, 2, 0, 0);
            CreateKatamino(8, 2, 3, 3, 4, 7, 2, 0, 0);
            CreateKatamino(8, 3, 3, 3, 2, 7, 1, 0, 0);
            CreateKatamino(8, 4, 3, 3, 2, 7, 4, 0, 0);
            CreateKatamino(8, 5, 3, 3, 2, 3, 6, 0, 0);
            CreateKatamino(8, 6, 3, 3, 2, 6, 3, 0, 0);
            CreateKatamino(8, 7, 3, 3, 3, 6, 2, 0, 0);
            CreateKatamino(8, 8, 3, 3, 6, 3, 2, 0, 0);

            CreateKatamino(9, 1, 3, 3, 1, 7, 4, 0, 0);
            CreateKatamino(9, 2, 3, 3, 4, 7, 1, 0, 0);
            CreateKatamino(9, 3, 3, 3, 3, 2, 6, 0, 0);
            CreateKatamino(9, 4, 3, 3, 6, 2, 3, 0, 0);

            CreateKatamino(10, 1, 3, 3, 1, 7, 1, 0, 0);
            CreateKatamino(10, 2, 3, 3, 4, 7, 4, 0, 0);
            CreateKatamino(10, 3, 3, 3, 2, 2, 7, 0, 0);
            CreateKatamino(10, 4, 3, 3, 7, 2, 2, 0, 0);

            CreateKatamino(11, 1, 3, 3, 1, 1, 7, 0, 0);
            CreateKatamino(11, 2, 3, 3, 4, 4, 7, 0, 0);
            CreateKatamino(11, 3, 3, 3, 7, 1, 1, 0, 0);
            CreateKatamino(11, 4, 3, 3, 7, 4, 4, 0, 0);

            CreateKatamino(12, 1, 3, 2, 5, 7, 0, 0, 0);
            CreateKatamino(12, 2, 3, 2, 7, 5, 0, 0, 0);
            CreateKatamino(12, 3, 2, 3, 3, 1, 3, 0, 0);
            CreateKatamino(12, 4, 2, 3, 3, 2, 3, 0, 0);

            return kataminos;
        }
    }
}
